<?php

require_once APPPATH . 'libraries/vendor/autoload.php';

use Request\Parser;
use Request\Exception\BadRequestException;
use Request\Exception\InternalServerErrorException;

/**
 * Request 参数生成类
 * - 负责根据提供的参数规则，进行参数创建工作，并返回错误信息
 * - 需要与参数规则配合使用
 */
class Request
{

    /**
     * @var CI_Controller $CI
     */
    protected $CI;

    /**
     * @var array $data 主数据源，接口原始参数
     */
    protected $data = array();

    /**
     * @var array $get 备用数据源 $_GET
     */
    protected $get = array();

    /**
     * @var array $post 备用数据源 $_POST
     */
    protected $post = array();

    /**
     * @var array $request 备用数据源 $_REQUEST
     */
    protected $request = array();

    /**
     * @var array $cookie 备用数据源 $_COOKIE
     */
    protected $cookie = array();

    /**
     * @var array $headers 备用数据源 请求头部信息
     */
    protected $headers;

    /**
     * @var string 接口类名
     */
    protected $apiName;

    /**
     * @var string 接口方法名
     */
    protected $actionName;

    /**
     * - 如果需要定制已知的数据源（即已有数据成员），则可重载此方法，例
     *
     * ```
     * class MyRequest extend Request{
     *     public function __construct($data = NULL) {
     *         parent::__construct($data);
     *
     *         // json处理
     *         $this->post = json_decode(file_get_contents('php://input'), TRUE);
     *
     *         // 普通xml处理
     *         $this->post = simplexml_load_string (
     *             file_get_contents('php://input'),
     *             'SimpleXMLElement',
     *             LIBXML_NOCDATA
     *         );
     *         $this->post = json_decode(json_encode($this->post), TRUE);
     *     }
     * }
     * ```
     * - 其他格式或其他xml可以自行写函数处理
     *
     * @param array $data 参数来源，可以为：$_GET/$_POST/$_REQUEST/自定义
     */
    public function __construct($data = NULL)
    {
        // 获取CI
        $this->CI =& get_instance();

        // 主数据源
        $this->data = $this->genData($data);

        // 备用数据源
        $this->get = $this->CI->input->get();
        $this->post = $this->CI->input->post();
        $this->delete = $this->CI->input->put();
        $this->put = $this->CI->input->put();
        $this->request = $_REQUEST;
        $this->cookie = $this->CI->input->cookie();

        $this->apiName = $this->CI->router->class . 'Controller';
        $this->actionName = $this->CI->router->method;
    }

    /**
     * 生成请求参数
     *
     * - 此生成过程便于项目根据不同的需要进行定制化参数的限制，如：如只允许接受POST数据，或者只接受GET方式的service参数，以及对称加密后的数据包等
     * - 如果需要定制默认数据源，则可以重载此方法
     *
     * @param array $data 接口参数包
     *
     * @return array
     */
    protected function genData($data)
    {
        if (!isset($data) || !is_array($data)) {
            return $_REQUEST;
        }

        return $data;
    }

    /**
     * 初始化请求Header头信息
     * @return array|false
     */
    protected function getAllHeaders()
    {
        if (function_exists('getallheaders')) {
            return getallheaders();
        }

        //对没有getallheaders函数做处理
        $headers = array();
        foreach ($_SERVER as $name => $value) {
            if (is_array($value) || substr($name, 0, 5) != 'HTTP_') {
                continue;
            }

            $headerKey = implode('-', array_map('ucwords', explode('_', strtolower(substr($name, 5)))));
            $headers[$headerKey] = $value;
        }

        return $headers;
    }

    /**
     * 根据规则获取参数
     * 根据提供的参数规则，进行参数创建工作，并返回错误信息
     *
     * @param $rule array('name' => '', 'type' => '', 'defalt' => ...) 参数规则
     *
     * @return mixed
     * @throws BadRequestException
     * @throws InternalServerErrorException
     */
    public function getByRule($rule)
    {
        $rs = NULL;

        if (!isset($rule['name'])) {
            throw new InternalServerErrorException(sprintf('参数规则缺少name'));
        }

        // 获取接口参数级别的数据集
        $data = !empty($rule['source']) && substr(php_sapi_name(), 0, 3) != 'cli'
            ? $this->getDataBySource($rule['source'])
            : $this->data;

        $rs = Parser::format($rule['name'], $rule, $data);

        if ($rs === NULL && (isset($rule['require']) && $rule['require'])) {
            throw new BadRequestException("缺少必要参数{$rule['name']}");
        }

        return $rs;
    }

    /**
     * 根据来源标识获取数据集
     * ```
     * |----------|---------------------|
     * | post     | $_POST              |
     * | get      | $_GET               |
     * | cookie   | $_COOKIE            |
     * | server   | $_SERVER            |
     * | request  | $_REQUEST           |
     * | header   | $_SERVER['HTTP_X']  |
     * |----------|---------------------|
     *
     * ```
     * - 当需要添加扩展其他新的数据源时，可重载此方法
     *
     * @throws InternalServerErrorException
     * @return array
     */
    protected function &getDataBySource($source)
    {
        switch (strtoupper($source)) {
            case 'POST' :
                return $this->post;
            case 'GET'  :
                return $this->get;
            case 'PUT' :
                return $this->put;
            case 'DELETE':
                return $this->delete;
            case 'COOKIE':
                return $this->cookie;
            case 'HEADER':
                if ($this->headers === NULL) {
                    $this->headers = $this->getAllHeaders();
                }
                return $this->headers;
            case 'SERVER':
                return $_SERVER;
            case 'REQUEST':
                return $this->request;
            default:
                break;
        }

        throw new InternalServerErrorException("参数规则中未知的数据源：{$source}");
    }


    /**
     * 取接口参数规则
     *
     * 主要包括有：
     * - 1、应用级统一接口参数规则，在config 文件 apiCommonRules中配置
     * - 2、接口级通用参数规则，在参数规则*中配置
     * - 3、接口级当前操作参数规则
     *
     * <b>当规则有冲突时，以后面为准。另外，被请求的函数名和配置的下标都转成小写再进行匹配。</b>
     *
     * @param string $apiName
     * @param string $actionName
     * @return array
     * @throws InternalServerErrorException
     */
    public function getApiRules($apiName = NULL, $actionName = NULL)
    {
        $rules = array();

        // 支持外部调用
        if(empty($apiName)){
            $apiName = $this->apiName;
        }

        // 支持外部调用
        if(empty($actionName)){
            $actionName = $this->actionName;
        }

        $actionName = strtolower($actionName);

        if (method_exists($apiName, 'getRules')) {
            $allRules = call_user_func(array($apiName, 'getRules'));
        } else {
            return [];
        }

        if (!is_array($allRules)) {
            $allRules = array();
        }
        $allRules = array_change_key_case($allRules, CASE_LOWER);

        if (isset($allRules[$actionName]) && is_array($allRules[$actionName])) {
            $rules = $allRules[$actionName];
        }

        if (isset($allRules['*'])) {
            $rules = array_merge($allRules['*'], $rules);
        }

        $apiCommonRules = $this->CI->config->item('common_rules');

        if (!empty($apiCommonRules) && is_array($apiCommonRules)) {
            $rules = array_merge($apiCommonRules, $rules);
        }

        return $rules;
    }


    /**
     * 获取全部接口参数
     * @throws InternalServerErrorException
     * @return array
     */
    public function getData()
    {
        $data = array();

        $rules_all = $this->getApiRules();

        foreach ($rules_all as $key => $rule) {
            $data[$key] = $this->getByRule($rule);
        }
        return $data;
    }
}
